﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Xml.Linq;
using AzukiMap.Map;
using AzukiMap.MapSource;
using AzukiMap.Tools;
using AzukiMap.Tools.Logger;
using GeoAPI.CoordinateSystems;
using GeoAPI.CoordinateSystems.Transformations;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.Geometries;
using ProjNet.CoordinateSystems.Transformations;

namespace AzukiMap.Layer
{
    public abstract class MapLayer : Canvas, ILayer, INotifyPropertyChanged
    {
        protected GeometryUtil _geoUtil;
        private string _layerName;
        private MapCoordinate _mapCoord;
        private IMapSource _mapSource;
        private IPolygon _maxExtent;
        private double _maxScale;
        private double _minScale;

        private Canvas _westCanvas;
        private Canvas _eastCanvas;

        protected XmlCommon _xmlCommon;

        private BitmapImage _dataTypeIcon;
        private string _iconSrc;
        private bool _isRendering;
        private bool _readable;
        private MapSourceTypes _type;
        private bool _writable;

        private bool _isDrawable = true;

        private bool _is180Connected;
        private double _eastEnd = double.PositiveInfinity;
        private double _westEnd = double.NegativeInfinity;


        #region Properties

        /// <summary>
        /// 
        /// </summary>
        public ICoordinateTransformation TransformMapToLayer { get; protected set; }

        public ICoordinateTransformation TransformLayerToMap { get; protected set; }
        protected internal LabelLayer LabelLayer { protected get; set; }
        protected internal virtual FrameworkElement CopyrightImage { get; protected set; }
        protected internal virtual FrameworkElement Copyright { get; protected set; }

        public IMapSource MapSource
        {
            get { return _mapSource; }
            protected set
            {
                _mapSource = value;
                //MaxExtent = _geoUtil.EnvelopeToPolygon(_mapSource.MaxExtent);
                MaxExtent = _mapSource.MaxExtent;
                MinScale = _mapSource.MinScale;
                MaxScale = _mapSource.MaxScale;

                SetCopyrightImage();
                SetCopyright();
                OnPropertyChanged("MapSource");
            }
        }

        private void Init()
        {
            Readable = true;
            Writable = true;
            IsRendering = false;
            MaxExtent = null;
            _minScale = AzukiMapSettings.DEFAULT_MINSCALE;
            _maxScale = AzukiMapSettings.DEFAULT_MAXSCALE;
            _geoUtil = new GeometryUtil();
            _xmlCommon = new XmlCommon();
#if SILVERLIGHT
            CacheMode = new BitmapCache();
#endif
            RenderedElements = new Dictionary<int, IList<FrameworkElement>>();
            IgnoreSrids = new List<int>();
            SetZIndex(0);
        }


        public bool NeedsTransform
        {
            get
            {
                if (MapCoord.Srid == MapSource.Srid)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
        }

        public Visibility LabelVisibility
        {
            get { return LabelLayer.Visibility; }
            set
            {
                LabelLayer.Visibility = value;
                OnPropertyChanged("LabelVisibility");
            }
        }

        public double MapWidth
        {
            get { return MapCoord.ScreenWidth; }
        }

        public double MapHeight
        {
            get { return MapCoord.ScreenHeight; }
        }

        public string LayerName
        {
            get { return _layerName; }
            set
            {
                _layerName = value;
                OnPropertyChanged("LayerName");
            }
        }

        public MapSourceTypes Type
        {
            get { return _type; }
            protected set
            {
                _type = value;
                OnPropertyChanged("Type");
            }
        }

        public virtual event EventHandler DrawingFinished;

        private void OnDrawingFinished(EventArgs e)
        {
            EventHandler finished = DrawingFinished;
            if (finished != null) finished(this, e);
        }

        public bool Readable
        {
            get { return _readable; }
            set
            {
                _readable = value;
                OnPropertyChanged("Readable");
            }
        }

        public bool Writable
        {
            get { return _writable; }
            set
            {
                _writable = value;
                OnPropertyChanged("Writable");
            }
        }

        public bool IsRendering
        {
            get { return _isRendering; }
            protected set
            {
                _isRendering = value;
                OnPropertyChanged("IsRendering");
                OnDrawingFinished(new EventArgs());
            }
        }

        public bool IsVisible
        {
            get { return (Visibility == Visibility.Visible); }
            set { Visibility = value ? Visibility.Visible : Visibility.Collapsed; }
        }

        public string IconSrc
        {
            set
            {
                _iconSrc = value;
                OnPropertyChanged("IconSrc");
            }
            get { return _iconSrc; }
        }

        public BitmapImage DataTypeIcon
        {
            get { return _dataTypeIcon; }
            set
            {
                _dataTypeIcon = value;
                OnPropertyChanged("DataTypeIcon");
            }
        }

        public IPolygon MaxExtent
        {
            get { return _maxExtent; }
            set
            {
                _maxExtent = value;
                TransformMaxExtent();
                OnPropertyChanged("MaxExtent");
            }
        }

        public virtual MapCoordinate MapCoord
        {
            get { return _mapCoord; }
            protected internal set
            {
                _mapCoord = value;
                SetCoordTransformation();
                MoveEastWestCanvases();
            }
        }

        public new Visibility Visibility
        {
            get { return (Visibility)GetValue(VisibilityProperty); }
            set
            {
                SetValue(VisibilityProperty, value);

                if (value == Visibility.Visible)
                {
                    // To draw only changed value of visible
                    if (Visibility != value)
                    {
                        Draw();
                    }
                }

                SetCopyrightVisibility(value);

                OnPropertyChanged("Visibility");
                OnPropertyChanged("IsVisible");
            }
        }

        public int ZIndex
        {
            get { return GetZIndex(); }
            set
            {
                SetZIndex(value);
                OnPropertyChanged("ZIndex");
            }
        }

        public double MaxScale
        {
            get { return _maxScale; }
            set
            {
                if (value <= 0)
                {
                    Logger.Error("Layer.MaxScale: MaxScale should be > 0.");
                    throw new ArgumentException("MaxScale should be > 0.", "MaxScale");
                }
                if (_minScale != 0 && _minScale > value)
                {
                    Logger.Error("Layer.MaxScale: MaxScale should be greater than MinScale.");
                    throw new ArgumentException("MaxScale should be greater than MinScale.", "MinScale");
                }
                _maxScale = value;
                OnPropertyChanged("MaxScale");
            }
        }

        public double MinScale
        {
            get { return _minScale; }
            set
            {
                if (value < 0)
                {
                    Logger.Error("Layer.minScale: MinScale should be >= 0.");
                    throw new ArgumentException("MinScale should be >= 0.", "MinScale");
                }
                if (_maxScale != 0 && _maxScale < value)
                {
                    Logger.Error("Layer.minScale: MinScale should be less than MinScale.");
                    throw new ArgumentException("MinScale should be less than MinScale.", "MinScale");
                }
                _minScale = value;
                OnPropertyChanged("MinScale");
            }
        }

        public int Srid
        {
            //get { return _mapCoord.Srid; }
            get { return MapSource.Srid; }
        }

        public FontFamily LabelFont
        {
            get; set;
        }

        public Brush LabelBrush
        {
            get; set;
        }

        public double LabelSize
        {
            get; set;
        }

        public bool LabelNeedsBackground
        {
            get; set;
        }

        public Brush LabelBackground
        {
            get; set;
        }

        public FontWeight LabelFontWeight
        {
            get; set;
        }

        public FontWeight LabelBackgroundFontWeight
        {
            get; set;
        }

        public double LabelBackgroundOffsetX
        {
            get; set;
        }

        public double LabelBackgroundOffsetY
        {
            get; set;
        }

        public bool DrawsAsProportionalSymbol { get; set; }

        public double ProportionalSymbolFactor { get; set; }

        protected void SetCopyrightVisibility(Visibility visibility)
        {
            if (CopyrightImage != null)
                CopyrightImage.Visibility = (NeedsToDraw() && IsInMaxExtent()) ? Visibility.Visible : Visibility.Collapsed;
            if (Copyright != null)
                Copyright.Visibility = (NeedsToDraw() && IsInMaxExtent()) ? Visibility.Visible : Visibility.Collapsed;
        }

        protected bool NeedsToDraw()
        {
            return (IsDrawable && (!IsRendering && IsVisible) && CheckScale());
        }

        protected bool IsInMaxExtent()
        {
            if (_mapSource.MaxExtent == null)
            {
                return true;
            }
            return _mapSource.MaxExtent.Intersects(_mapCoord.CurrentExtent);
        }

        public bool IsDrawable
        {
            get { return _isDrawable; }
            set { _isDrawable = value; }
        }

        public Dictionary<int, IList<FrameworkElement>> RenderedElements
        {
            get; set;
        }

        public IList<int> IgnoreSrids
        {
            get; set;
        }

        public bool Is180Connected
        {
            get { return _is180Connected; }
            set
            {
                _is180Connected = value;
                _datelineConnection.Is180Connected = value;
                CreateEastWestCanvas(value);
            }
        }

        /// <summary>
        /// Max X for connecting the east + west canvases
        /// </summary>
        public double EastEnd
        {
            get { return _eastEnd; }
            set
            {
                _eastEnd = value;
                _datelineConnection.EastEnd = value;
            }
        }

        /// <summary>
        /// Min X for connection the east + west canvases.
        /// </summary>
        public double WestEnd
        {
            get { return _westEnd; }
            set
            {
                _westEnd = value;
                _datelineConnection.WestEnd = value;
            }
        }

        public double WorldWidth
        {
            get
            {
                return _eastEnd - _westEnd;
            }
        }

        public Canvas WestCanvas
        {
            get { return _westCanvas; }
        }

        public Canvas EastCanvas
        {
            get { return _eastCanvas; }
        }

        public IMultiPolygon CurrentExtent
        {
            get { return GetCurrentExtent(); }
        }

        public IMultiPolygon RenderedExtent
        {
            get { return GetRenderedExtent(); }
        }

        public ShaderEffect RasterEffect
        {
            get; set;
        }

        private IPoint _mapCenter;

        #endregion

        protected MapLayer()
        {
            LayerName = GetDefaultName();

            Init();
        }

        protected MapLayer(IMapSource mapSource)
        {
            Type = mapSource.Type;
            LayerName = GetDefaultName();
            MapSource = mapSource;

            Init();
        }

        protected MapLayer(IMapSource mapSource, string name)
        {
            Type = mapSource.Type;
            LayerName = name;
            MapSource = mapSource;

            Init();
        }

        // TODO: remove data type.
        protected MapLayer(MapSourceTypes dataType, IMapSource mapSource)
        {
            Type = dataType;
            LayerName = GetDefaultName();
            MapSource = mapSource;

            Init();
        }

        // TODO: remove data type.
        protected MapLayer(MapSourceTypes dataType, IMapSource mapSource, string name)
        {
            Type = dataType;
            LayerName = name;
            MapSource = mapSource;

            Init();
        }

        #region ILayer Members

        public virtual void Show()
        {
            Visibility = Visibility.Visible;
        }

        public virtual void Hide()
        {
            Visibility = Visibility.Collapsed;
        }

        public virtual void Draw()
        {
            SetCopyrightVisibility(IsDrawable ? Visibility.Visible : Visibility.Collapsed);
        }

        public virtual void Redraw()
        {
            Clear();
            _mapCoord.ClearLastExtent();

            Draw();
        }

        public virtual void Clear()
        {
            if (_is180Connected)
            {
                _eastCanvas.Children.Clear();
                _westCanvas.Children.Clear();
            }
            else
            {
                Children.Clear();
            }
            if (_mapCoord != null)
                _mapCoord.ClearLastExtent();
        }

        public virtual void Parse(XElement xmlSource)
        {
            LayerName = _xmlCommon.GetStringAttr(xmlSource, "Name");
            Type = (MapSourceTypes)_xmlCommon.GetEnumAttr(xmlSource, "Type", typeof(MapSourceTypes));
            Readable = _xmlCommon.GetBoolAttr(xmlSource, "Readable");
            Writable = _xmlCommon.GetBoolAttr(xmlSource, "Writable");
            MaxExtent = (IPolygon)_xmlCommon.GetGeomElement(xmlSource, "MaxExtent");
            _maxScale = _xmlCommon.GetDoubleAttr(xmlSource, "MaxScale");
            _minScale = _xmlCommon.GetDoubleAttr(xmlSource, "MinScale");
            Visibility = (_xmlCommon.GetBoolAttr(xmlSource, "Visible")) ? Visibility.Visible : Visibility.Collapsed;
            Opacity = _xmlCommon.GetDoubleAttr(xmlSource, "Opacity");
            //_mapSource.Parse(xmlSource.Element("MapSource"));
        }

        public virtual XElement ToXml()
        {
            return ToXml("Layer");
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        public virtual void SetCoordTransformation()
        {
            if ( IgnoreSrids.Contains(MapCoord.Srid))
            {
                IsDrawable = false;
                return;
            }

            IsDrawable = true;

            if (Srid == MapCoord.Srid)
            {
                return;
            }

            ICoordinateSystem mapCoordSystem = SridParser.GetCoordinateSystemFromSrid(MapCoord.Srid);
            ICoordinateSystem layerCoordSystem = SridParser.GetCoordinateSystemFromSrid(Srid);
            TransformMapToLayer = new CoordinateTransformationFactory().CreateFromCoordinateSystems(mapCoordSystem,
                                                                                                    layerCoordSystem);
            TransformLayerToMap = new CoordinateTransformationFactory().CreateFromCoordinateSystems(layerCoordSystem,
                                                                                                    mapCoordSystem);
            TransformLocalItems(MapCoord.Srid);
            TransformMaxExtent();
        }

        protected IPoint GetPointOnMapSource(IPoint pointOnMap)
        {
            return TransformUtil.Transform(pointOnMap, TransformMapToLayer);
        }

        protected IPoint GetPointOnMap(IPoint pointOnMapSource)
        {
            return TransformUtil.Transform(pointOnMapSource, TransformLayerToMap);
        }

        protected IPolygon GetPolygonOnMapSource(IPolygon polyOnMap)
        {
            return TransformUtil.Transform(polyOnMap, TransformMapToLayer);
        }

        protected IPolygon TransformPolygonOnMap(IPolygon polyOnMapSource)
        {
            return TransformUtil.Transform(polyOnMapSource, TransformMapToLayer);
        }

        private string GetDefaultName()
        {
            return string.Format("{0}_{1}", Type, Serial.GetSerialNumber());
        }

        private int GetZIndex()
        {
            return (int)GetValue(ZIndexProperty);
        }

        private void SetZIndex(int zIndex)
        {
            SetValue(ZIndexProperty, zIndex);
        }

        protected bool CheckScale()
        {
            // check if screen size is initialized. 
            if (MapCoord == null)
            {
                return false;
            }
            if (MapCoord.ScreenWidth == AzukiMapSettings.INIT_DUMMY_SCREEN_SIZE ||
                MapCoord.ScreenHeight == AzukiMapSettings.INIT_DUMMY_SCREEN_SIZE)
            {
                return false;
            }
            if (MapCoord.GeoScale < _minScale || MapCoord.GeoScale < MapSource.MinScale)
            {
                return false;
            }
            if (MapCoord.GeoScale > _maxScale || MapCoord.GeoScale > MapSource.MaxScale)
            {
                return false;
            }
            return true;
        }

        protected bool CheckMaxExtent()
        {
            bool inMaxExtent = true;
            if (MaxExtent == null)
            {
                return true;
            }
            if (CurrentExtent == null)
            {
                return false;
            }

            if (CurrentExtent.Disjoint(MaxExtent))
            {
                inMaxExtent = false;
            }
            return inMaxExtent;
        }

        /// <summary>
        /// Create a approximate projected extent from 100 points on each edges.
        /// </summary>
        /// <param name="sourceExtent">extent by the map projection</param>
        /// <returns>projected extent to request</returns>
        protected IEnvelope GetProjectedExtent(IEnvelope sourceExtent)
        {
            if (!NeedsTransform)
            {
                return sourceExtent;
            }

            // get a transformed extent from 100 sample points on each edges.
            int interval = 100;
            double minX = double.MaxValue;
            double minY = double.MaxValue;
            double maxX = double.MinValue;
            double maxY = double.MinValue;
            for (double x = sourceExtent.MinX; x <= sourceExtent.MaxX; x += sourceExtent.Width / interval)
            {
                for (double y = sourceExtent.MinY; y <= sourceExtent.MaxY; y += sourceExtent.Height / interval)
                {
                    double[] layerPoint = TransformMapToLayer.MathTransform.Transform(new[] { x, y });
                    minX = (layerPoint[0] < minX) ? layerPoint[0] : minX;
                    minY = (layerPoint[1] < minY) ? layerPoint[1] : minY;
                    maxX = (maxX < layerPoint[0]) ? layerPoint[0] : maxX;
                    maxY = (maxY < layerPoint[1]) ? layerPoint[1] : maxY;
                }
            }
            return new Envelope(minX, maxX, minY, maxY);
        }

        protected void TransformMaxExtent()
        {
            if (_maxExtent != null && _mapCoord != null)
                _maxExtent = TransformUtil.TransformExtentPolygon(_maxExtent, _mapCoord.Srid);
        }

        private void SetCopyrightImage()
        {
            if (MapSource.SourceLogo != null)
            {
                CopyrightImage = new Image
                                     {
                                         Source = new BitmapImage(MapSource.SourceLogo)
                                     };
            }
        }

        private void SetCopyright()
        {
            if (!string.IsNullOrEmpty(MapSource.Copyright))
            {
                Copyright = new TextBlock
                                {
                                    Text = MapSource.Copyright,
                                    FontSize = 9,
                                    Foreground = new SolidColorBrush(Colors.Gray),
                                };
            }
        }

        protected virtual void TransformLocalItems(int targetSrid)
        {
        }

        public virtual XElement ToXml(string elementName)
        {
            var layerXml = new XElement(elementName,
                                        _xmlCommon.CreateAttr("Name", LayerName),
                                        _xmlCommon.CreateAttr("Type", Type),
                                        _xmlCommon.CreateAttr("Readable", Readable),
                                        _xmlCommon.CreateAttr("Writable", Writable),
                                        _xmlCommon.GeometryToXml("MaxExtent", MaxExtent),
                                        _xmlCommon.CreateAttr("MaxScale", _maxScale),
                                        _xmlCommon.CreateAttr("MinScale", _minScale),
                                        _xmlCommon.CreateAttr("Opacity", Opacity),
                                        _xmlCommon.CreateAttr("Visible", IsVisible),
                                        MapSource.ToXml()
                );
            return layerXml;
        }

        /*
        protected MapSourceTypes _layer;
        protected string _name;
        protected bool _readable;
        protected bool _writable;
        protected bool _isRendering;
        protected Geometries.Polygon _maxExtent;
        protected double _maxScale;
        protected double _minScale;
        protected MapCanvas _mapCoord;
        protected GeometryUtil _geoUtil;
        protected IMapSource _mapSource;
         */

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Called by MapControl when the map scale changed.
        /// </summary>
        public virtual void MapScaleChanged(object sender, MapScaleChangedArgs e)
        {

        }

        public List<FrameworkElement> FindElements(System.Windows.Point point)
        {
            point.X = point.X - this.MapCoord.AdjX;
            point.Y = point.Y - this.MapCoord.AdjY;

            System.Windows.Rect rect = new Rect(point, new Size(100, 100));

            List<FrameworkElement> list = new List<FrameworkElement>();

            IEnumerable<UIElement> elms = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(
                rect, this);

            foreach (UIElement elm in elms)
            {
                FrameworkElement fe = elm as FrameworkElement;
                if (fe == null) continue;

                list.Add(fe);
            }

            foreach (UIElement elm in Children)
            {
                elms = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(
                    rect, elm);

                foreach (UIElement e in elms)
                {
                    FrameworkElement fe = e as FrameworkElement;
                    if (fe == null) continue;
                    list.Add(fe);
                }

                //                list.Add(elms[0] as FrameworkElement);
            }

            return list;
        }

        private void CreateEastWestCanvas(bool is180Connected)
        {
            if (is180Connected)
            {
                _westCanvas = new Canvas();
                _eastCanvas = new Canvas();

                Children.Add(_westCanvas);
                Children.Add(_eastCanvas);

            }
        }

        public void MoveEastWestCanvases()
        {
            IPoint mapCenter = _mapCoord.GeoCenter;
            if (_is180Connected)
            {
                double mapWidth = EastEnd - WestEnd;
                // east canvas num 0 is between -90 to 270
                var eastCanvasNum = (int)Math.Floor((mapCenter.X + mapWidth / 4) / mapWidth);
                // west canvas num 0 is between -270 to 90
                var westCanvasNum = (int)Math.Floor((mapCenter.X + mapWidth / 4 * 3) / mapWidth);

                //double pixPointOriginEastLeft = MapCoord.GetPixPointFromGeo(mapWidth * eastCanvasNum, mapCenter.Y).X;
                //double pixPointOriginWestLeft = MapCoord.GetPixPointFromGeo(mapWidth * westCanvasNum, mapCenter.Y).X;
                double pixPointOriginEastLeft = (mapWidth / MapCoord.ScreenScale) * eastCanvasNum;
                double pixPointOriginWestLeft = (mapWidth / MapCoord.ScreenScale) * westCanvasNum;

                if (Math.Abs((double)_eastCanvas.GetValue(LeftProperty) - pixPointOriginEastLeft) > 0.1)
                    _eastCanvas.SetValue(LeftProperty, pixPointOriginEastLeft);

                if (Math.Abs((double)_westCanvas.GetValue(LeftProperty) - pixPointOriginWestLeft) > 0.1)
                    _westCanvas.SetValue(LeftProperty, pixPointOriginWestLeft);
            }

        }

        public IList<FrameworkElement> GetRenderedElementById(int id)
        {
            if (RenderedElements.ContainsKey(id)) 
                 return RenderedElements[id];

            return null;
        }

        private IMultiPolygon GetCurrentExtent()
        {
            if (_mapCoord == null)
                return null;

            if (_is180Connected)
            {
                var envelope = _mapCoord.CurrentExtent.EnvelopeInternal;
                return RegulateExtent(envelope.MinX, envelope.MinY, envelope.MaxX, envelope.MaxY);
            }
            return new MultiPolygon(new[] { _mapCoord.CurrentExtent });
        }

        private IMultiPolygon GetRenderedExtent()
        {
            if (_is180Connected)
            {
                if (_mapCoord.RenderedExtent == null)
                    return null;

                var envelope = _mapCoord.RenderedExtent.EnvelopeInternal;
                return RegulateExtent(envelope.MinX, envelope.MinY, envelope.MaxX, envelope.MaxY);
            }
            return new MultiPolygon(new[] { _mapCoord.RenderedExtent });
        }

        private readonly DatelineConnection _datelineConnection = new DatelineConnection();

        private IMultiPolygon RegulateExtent(double minX, double minY, double maxX, double maxY)
        {
            return _datelineConnection.RegulateExtent(minX, minY, maxX, maxY);
        }

        public override string ToString()
        {
            return LayerName;
        }

    }
}